//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<ShellConsole.cpp>
///	@path	~/src/appear/xwin/
///	@date	2007/12/11
///	@desc	Shell-like script console.

#define _FULLAPI
#include "config/config.h"

#include "engine/xcore/xeres_engine.h"
#include "engine/xcore/event_center.h"

#include "appear/xwin/ShellConsole.h"

namespace xeres {

	// ShellConsole
	ShellConsole::ShellConsole( void )
		: m_actived( false )
		, m_reset( false )
		, m_histroySelected( false )
		, m_editHeight( 25 )
		, m_traceCallId( -1 )
		, m_historyMax( 20 )
		, m_historyCurrent( 0 )
		, m_histroyIndex( 0 )
	{
		m_prompt = new CVar;
		m_interpName = new CVar;
	}

	// ~ShellConsole
	ShellConsole::~ShellConsole( void )
	{
		m_prompt = NULL;
		m_interpName = NULL;
	}

	// Initialize
	bool ShellConsole::Initialize( Engine& engine )
	{
		if( m_traceCallId != -1 )
		{
			TRACE_ERROR( _S("ShellConsole::Initialize: Console has been initialized.") );
			return false;
		}

		// add handler
		m_traceCallId = add_trace_handler( bind( &ShellConsole::TraceString , this , _1 , _2 ) );

		const bool sel = ( engine.GetRenderer()->GetColorOrder() != COLOR_ORDER_ARGB );

		// make quads
		m_quadLayout = engine.GetRenderer()->DeclareVertexLayout( vd::ScreenColor::decl() );
		m_quads.Initialize( engine , m_quadLayout );
		vd::ScreenColor board_vert[] = {
			// 1 trace overlay
			{ 0 , 0 , 0.5f, 1.0f, d3d_select_color(0xaa333333,sel), },
			{ 0 , 0 , 0.5f, 1.0f, d3d_select_color(0xaa333333,sel), },
			{ 0 , 0 , 0.5f, 1.0f, d3d_select_color(0xaa333333,sel), },
			{ 0 , 0 , 0.5f, 1.0f, d3d_select_color(0xaa333333,sel), },
			// 2 shell overlay
			{ 0 , 0 , 0.4f, 1.0f, d3d_select_color(0xaa000033,sel), },
			{ 0 , 0 , 0.4f, 1.0f, d3d_select_color(0xaa000033,sel), },
			{ 0 , 0 , 0.4f, 1.0f, d3d_select_color(0x00000000,sel), },
			{ 0 , 0 , 0.4f, 1.0f, d3d_select_color(0x00000000,sel), },
		};
		m_quads.Copy( board_vert );

		// make edit box
		m_editLayout = engine.GetRenderer()->DeclareVertexLayout( vd::ScreenTexture::decl() );
		m_editQuad.Initialize( engine , m_editLayout );
		vd::ScreenTexture edit_vert[] = {
			{ 0 , 0 , 0.3f, 1.0f, 0.f , 0.f, },
			{ 0 , 0 , 0.3f, 1.0f, 1.f , 0.f, },
			{ 0 , 0 , 0.3f, 1.0f, 0.f , 1.f, },
			{ 0 , 0 , 0.3f, 1.0f, 1.f , 1.f, },
		};
		m_editQuad.Copy( edit_vert );
		Ref<IEffect> effect = engine.LoadEffect( _S("ui/ui_sprite.fx") , NULL , false );
		Ref<ITechnique> tech = effect->GetTechnique( "RenderFrame" );
		m_editAdapter = engine.GetRenderer()->LinkEffect( tech , m_editLayout );
		m_editShader = tech;
		m_editVar = effect->GetVariableByName( "FrameTexture" );

		return true;
	}

	// Finalize
	void ShellConsole::Finalize( void )
	{
		m_interps.clear();
		m_prompt->SetString( _S("") );
		m_interpName->SetString( _S("") );

		// MUST !!!! release all references
		m_layerTexture = NULL;
		m_canvas = NULL;
		m_traceFont = NULL;
		m_traceText = NULL;
		m_shellText = NULL;
		m_promptText = NULL;
		m_quadLayout = NULL;
		m_editLayout = NULL;
		m_editAdapter = NULL;
		m_editShader = NULL;
		m_editVar = NULL;

		m_quads.Finalize();
		m_editQuad.Finalize();

		// delete handler
		del_trace_handler( m_traceCallId );
	}

	// PrepareConsoleResource
	void ShellConsole::PrepareConsoleResource( RenderContext& rc )
	{
		// Calculate regions.
		int split = m_rect.height() - 20 - m_editHeight;
		int prompt_width = 53;

		Rect<int> board( 18 , 18 , m_rect.right() - 10 , split );

		int trace_split = board.height() / 2;
		Rect<int> trace( 15 , board.top() , board.right() , board.top() + trace_split );
		Rect<int> shell( 15 , trace.bottom() + 3 , board.right() , board.bottom() );

		Rect<int> prompt( 0 , split , prompt_width , m_rect.bottom() - 10 );
		Rect<int> interp( -2 , split - 1 , prompt_width - 5 , m_rect.bottom() - 30 );
		Rect<int> editbox_layer( prompt_width - 10 , split , m_rect.right() - prompt_width , m_rect.bottom() - 10 );
		Rect<int> editbox( 0 , 0 , editbox_layer.width() , editbox_layer.height() );

		m_editRect = editbox_layer;

		if( m_canvas.IsNull() )
		{
			Pair<int> size = editbox_layer.get_max() - editbox_layer.get_min();
			// Create all new elements.
			m_layerTexture = new CCompositeTexture;
			m_layerTexture->Reset( rc.GetRenderer() , size , false );

			m_canvas = new CCanvas;
			m_canvas->Initialize( rc.GetRenderer() );

			m_traceFont = rc.GetRenderer()->CreateFont(
				_S("Courier New") , -14 , 0 , 400 , false , true , true );

			m_traceText = new CCanvasRollingText( m_traceFont );
			m_shellText = new CCanvasRollingText( m_traceFont );

			m_promptText = new CCanvasText( m_prompt , m_traceFont ,
				DT_SINGLELINE | DT_VCENTER | DT_CENTER | DT_EXPANDTABS , Color4( 1 , 1 ) );

			m_interpNameFont = rc.GetRenderer()->CreateFont(
				_S("Tahoma") , -12 , 0 , 400 , true , true , true );

			m_interpNameText = new CCanvasText( m_interpName , m_interpNameFont ,
				DT_SINGLELINE | DT_VCENTER | DT_CENTER , Color4( 1 , 0.5 ) );

			m_canvas->AddElement( m_traceText );
			m_canvas->AddElement( m_shellText );
			m_canvas->AddElement( m_interpNameText );
			m_canvas->AddElement( m_promptText );

			m_edit.SetFont( m_traceFont );
			m_edit.SetText( _S("") , false );
		}
		
		m_traceText->SetRect( trace );
		m_shellText->SetRect( shell );
		m_edit.OnCtrlSize( editbox );
		m_promptText->SetRect( prompt );
		m_interpNameText->SetRect( interp );

		PrepareOverlay( rc , m_rect , split , editbox_layer );

		m_reset = false;
	}

	// Tick
	void ShellConsole::Tick( Engine& engine )
	{
	}

	// Render
	void ShellConsole::Render( RenderContext& rc )
	{
		if( rc.ResX<int>() != m_rect.width() || rc.ResY<int>() != m_rect.height() )
		{
			// rebuild them
			m_layerTexture = NULL;
			m_canvas = NULL;
			m_traceFont = NULL;
			m_traceText = NULL;
			m_shellText = NULL;
			m_promptText = NULL;

			m_reset = true;
			m_rect.set( 0 , 0 , rc.ResX<int>() , rc.ResY<int>() );
			PrepareConsoleResource( rc );
		}

		rc.ClearDepth( 1 );

		if( m_edit.NeedUpdate() )
		{
			Layer& layer = m_layerTexture->GetLayer();
			layer.Clear();
			m_edit.OnPaint( layer );
			m_layerTexture->Update( m_edit.GetRect() );
		}

		// render overlay
		RefWeak<IShaderAdapter> adapter = m_quadLayout->GetDefaultAdapter();
		RefWeak<IShader> shader = adapter->AdaptShader( rc );
		adapter->AssembleShader( rc );
		m_quads.Establish( rc );
		UINT pass = 0;
		do 
		{
			shader->Begin( pass , rc );
			m_quads.Draw( rc );
			shader->End( pass , rc );
		}while( pass );

		// render canvas
		m_canvas->Render( rc );

		m_editVar->SetByTexture( m_layerTexture );
		// render edit
		m_editAdapter->AssembleShader( rc );
		m_editQuad.Establish( rc );
		pass = 0;
		do 
		{
			m_editShader->Begin( pass , rc );
			m_editQuad.Draw( rc );
			m_editShader->End( pass , rc );
		}while( pass );
		m_editVar->SetByTexture( NULL );
	}

	// OnKeyInput
	HandleInput ShellConsole::OnKeyInput( const KeyInput& input )
	{
		// short cuts
		if( m_hotkey.OnKeyInput( input ) == INPUT_HANDLED )
			return INPUT_HANDLED;

		if( input.GetKey() == KeyInput::KEY_RETURN )
		{
			if( ( input.GetState() & Input::INPUT_STATE_KEY_COMBINER ) == 0 )
			{
				if( !input.IsPressed() )
					OnExecute();
			}
			return INPUT_HANDLED;
		}
		else if( input.GetKey() == KeyInput::KEY_UPARROW )
		{
			if( m_history.size() > 0 &&
				input.IsReleased() &&
				( input.GetState() & Input::INPUT_STATE_KEY_COMBINER ) == 0 )
			{
				if( m_histroyIndex == -1 )
					m_histroyIndex = m_historyCurrent;
				else
					--m_histroyIndex;
				if( m_histroyIndex < 0 )
					m_histroyIndex = (int)m_history.size() - 1;
				else if( m_histroyIndex >= (int)m_history.size() )
					m_histroyIndex = 0;
				//printf( "history %i %u(%i)\n" , m_histroyIndex , m_historyCurrent , m_history.size() );

				m_edit.SetText( m_history[m_histroyIndex] , false );
			}
			return INPUT_HANDLED;
		}
		else if( input.GetKey() == KeyInput::KEY_DOWNARROW )
		{
			if(	m_history.size() > 0 &&
				input.IsReleased() &&
				( input.GetState() & Input::INPUT_STATE_KEY_COMBINER ) == 0 )
			{
				if( m_histroyIndex == -1 )
					m_histroyIndex = m_historyCurrent;
				else
					++m_histroyIndex;
				if( m_histroyIndex >= (int)m_history.size() )
					m_histroyIndex = 0;
				else if( m_histroyIndex < 0 )
					m_histroyIndex = (int)m_history.size() - 1;

				//printf( "history %i %u(%i)\n" , m_histroyIndex , m_historyCurrent , m_history.size() );

				m_edit.SetText( m_history[m_histroyIndex] , false );
			}
			return INPUT_HANDLED;
		}

		if( m_edit.OnKeyDown( input ) )
			return INPUT_HANDLED;

		return INPUT_UNHANDLED;
	}

	// OnMouseInput
	HandleInput ShellConsole::OnMouseInput( const MouseInput& input )
	{
		if( m_layerTexture.IsValid() )
		{
			Pair<int> pt = input.GetPoint();
			if( m_editRect.is_inside( pt ) )
			{
				pt -= m_editRect.get_min();
				return m_edit.OnMouseInput( input , pt.x , pt.y );
			}
		}
		return INPUT_UNHANDLED;
	}

	// OnActive
	void ShellConsole::OnActive( bool is_active )
	{
		m_actived = is_active;
	}

	// TraceString
	void ShellConsole::TraceString( TraceLevel level , const wchar_t * msg )
	{
		static const Color4 color[] = 
		{
			Color4( 1.0f , 1.0f , 0.9f , 1 ) , // trace;
			Color4( 0.9f , 0.6f , 0.2f , 1 ) , // debug
			Color4( 1.0f , 1.0f , 1.0f , 1 ) , // info
			Color4( 1.0f , 0.3f , 0.2f , 1 ) , // warning
			Color4( 1.0f , 0.1f , 0.1f , 1 ) , // error
			Color4( 1.0f , 0.0f , 0.0f , 1 ) , // fatal
			Color4( 0.2f , 0.3f , 0.9f , 1 ) , // console
			Color4( 0.0f , 0.1f , 0.1f , 1 ) , // assert
			Color4( 1.0f , 1.0f , 1.0f , 1 ) , // stdout
			Color4( 1.0f , 0.1f , 0.1f , 1 ) , // stderr
		};

		if( m_traceText.IsValid() )
		{
			if( level == TRACE_LV_STDOUT || level == TRACE_LV_STDERR )
				m_shellText->PrintLine( WString(msg) , color[level] );
			else
				m_traceText->PrintLine( WString(msg) , color[level] );
		}
	}

	// OnExecute
	void ShellConsole::OnExecute( void )
	{
		if( m_currentInterp.IsValid() )
		{
			WString prompt;
			m_prompt->GetString( prompt );
			const WString& input = m_edit.GetText();
			bool noinput = input == m_cue;
			
			if( !noinput )
				m_shellText->PrintLine( prompt + _S(" ") + input + _S("\n") , Color4(1,1) );

			switch( m_currentInterp->RunInteractive( input , m_cue ) )
			{
			case INTERPRET_STMT:
			case INTERPRET_OK:
				break;
			}
			// add input to history.
			if( !noinput )
			{
				if( m_history.size() < m_historyMax )
				{
					m_histroyIndex = m_historyCurrent = (int)m_history.size();
					m_history.push_back( input );
				}
				else
				{
					++m_historyCurrent;
					if( m_historyCurrent >= (int)m_history.size() )
						m_historyCurrent = 0;
					m_history[m_historyCurrent] = input;
				}
			}
			else
			{
				// hide myself
			}
			m_prompt->SetString( m_currentInterp->QueryPrompt() );
			m_edit.SetText( m_cue , false );
		}
		else
		{
			m_cue.clear();
			m_edit.ClearText();
		}
		++m_histroyIndex;
	}

	// AddInterpreter
	bool ShellConsole::AddInterpreter( const WString& name ,
		RefWeak<IScriptInterpreter> interp )
	{
		m_interps[ name ] = interp;
		m_currentInterp = interp;
		m_prompt->SetString( interp->QueryPrompt() );
		m_interpName->SetString( name + _S(": ") );
		m_cue.clear();
		m_interpreterName = name;

		return true;
	}

	// PrepareOverlay
	void ShellConsole::PrepareOverlay( RenderContext& rc , const Rect<int>& rect ,
		int split , const Rect<int>& input )
	{
		// 1
		m_quads[0].x = (float)rect.left();  m_quads[0].y = (float)rect.bottom();
		m_quads[1].x = (float)rect.left();  m_quads[1].y = (float)rect.top();
		m_quads[2].x = (float)rect.right(); m_quads[2].y = (float)rect.bottom();
		m_quads[3].x = (float)rect.right(); m_quads[3].y = (float)rect.top();
		// 2
		m_quads[4].x = (float)rect.left();  m_quads[4].y = (float)input.bottom();
		m_quads[5].x = (float)rect.left();  m_quads[5].y = (float)input.top();
		m_quads[6].x = (float)rect.right(); m_quads[6].y = (float)input.bottom();
		m_quads[7].x = (float)rect.right(); m_quads[7].y = (float)input.top();
		// process
		rc.ProcessRasterCoord( &(m_quads[0]) , sizeof(vd::ScreenColor) , 8 );
		// commit
		m_quads.Commit();
		// edit rect
		float dx = float( input.right() - input.left() );
		float dy = float( input.bottom() - input.top() );
		float dw = 1 / dx * 0.5f;
		float dh = 1 / dy * 0.5f;
		// vertex
		m_editQuad[0].x = (float)input.left();  m_editQuad[0].y = (float)input.top();
		m_editQuad[1].x = (float)input.right(); m_editQuad[1].y = (float)input.top();
		m_editQuad[2].x = (float)input.left();  m_editQuad[2].y = (float)input.bottom();
		m_editQuad[3].x = (float)input.right(); m_editQuad[3].y = (float)input.bottom();
		// uv
		m_editQuad[0].u = 0.f + dw; m_editQuad[0].v = 0.f + dh;
		m_editQuad[1].u = 1.f + dw; m_editQuad[1].v = 0.f + dh;
		m_editQuad[2].u = 0.f + dw; m_editQuad[2].v = 1.f + dh;
		m_editQuad[3].u = 1.f + dw; m_editQuad[3].v = 1.f + dh;
		// process
		rc.ProcessRasterCoord( &(m_editQuad[0]) , sizeof(vd::ScreenTexture) , 4 );
		// commit
		m_editQuad.Commit();
	}
	// ActiveInterpreter
	void ShellConsole::ActiveInterpreter( const WString& interpreter )
	{
		dict<WString , Ref<IScriptInterpreter> >::iterator it = m_interps.find( interpreter );
		if( it != m_interps.end() )
		{
			m_currentInterp = it->second;

			m_prompt->SetString( m_currentInterp->QueryPrompt() );
			m_interpName->SetString( interpreter + _S(": ") );
			m_cue.clear();
			m_interpreterName = interpreter;
		}
		else
		{
			TRACE_ERROR( _S("ShellConsole::ActiveInterpreter: No interpreter registerd '%s'.") , interpreter.c_str() );
		}
	}
	// GetActiveInterpreter
	const WString& ShellConsole::GetActiveInterpreter( void ) const
	{
		return m_interpreterName;
	}
} // namespace xeres
